/// 变更请求
module jinji.post.cr;

import jinji.common.query,
jinji.common.type;
import jinji.post.model,
jinji.post.service,
jinji.rpc,
jinji.tag.service,
jinji.user.service;

alias CR = ChangeRequest;

extern (C):

mixin getObject!(CR, "getCR", "查询变更请求", "变更请求", "updated_at desc");

/// 修改变更请求
@Action void setCR(ref Request req, @"变更请求id"long id, @"标题" string title, @"描述" string desc) {
	auto q = query!(SB.select!(CR.creator, CR.status)
			.where("id=$1"))(id);
	checkArg(id.stringof, !q.empty, "变更请求不存在");
	if (req.uid != q.get!long)
		req.require(Perm.editCR);
	const status = q.get!(CRStatus, 1);
	checkArg(id.stringof, status == CRStatus.pending, "变更请求已被处理");
	exec!(SB.update!CR
			.set("title=$1,desc=$2,updated_at=$3").where("id=$4"))(
		parseText!"标题"(title), parseDesc(desc), now, id);
}

/// 更新变更请求状态
@Action void updateCR(ref Request req, @"变更请求id"long id, @"变更请求状态"int status) {
	auto q = query!(SB.select!(CR.creator, CR.pid, CR.tags)
			.where("id=$1"))(id);
	checkArg(id.stringof, !q.empty, "变更请求不存在");
	checkArgRange(status.stringof, CRStatus.min <= status && status <= CRStatus.max, "无效的状态");

	const creator = q.get!(long, 0);
	// 只有创建者可以编辑未被拒绝的变更请求
	if ((req.uid == creator && status != CRStatus.rejected) ||
		req.uid != creator) {
		req.require(Perm.editCR);
	}

	const time = now;
	exec!(SB.update!CR
			.set("status=$1,operator=$2,updated_at=$3").where(
			"id=$4"))(status, req.uid, time, id);
	if (status == CRStatus.accepted) {
		const pid = q.get!(Id, 1);
		const tags = q.get!(Tags, 2);
		const post = getPostByTags(tags);
		checkArg(null, !post, text("标签", tags, "已被", post, "占用"));
		updateTags(time, pid, tags, getTags(pid));
	}
}

/++
发起变更请求
Params:
	creator = 创建者
	pid = 帖子id
	tags = 标签
	title = 标题
	desc = 描述
Returns: 变更请求id
+/
Id requestChange(Id creator, Id pid, string tags, string title, Desc desc) {
	// TODO
	if (tags)
		tags = applyTagAliases(parseTags(tags, (tag) {
				if (tag.exists)
					return tag;
				// BUG: tags.stringof
				//checkArg("tags", tag.exists, text("标签", tag, "不存在"));
				return null;
			}));
	else
		tags = "";
	const oldTags = getTags(pid);
	checkArg(tags.stringof, tags != oldTags, "标签未变更");

	db.begin();
	scope (failure)
		db.rollback();
	const time = now;
	auto status = CRStatus.pending;
	// 自动接受
	if (canAutoAcceptCR(pid, creator, tags, oldTags)) {
		status = CRStatus.accepted;
		updateTags(time, pid, cast(Tags)tags, oldTags);
	}
	exec!("INSERT INTO change_request(title,desc,status,pid,tags,creator,created_at,updated_at)" ~
			"VALUES($1,$2,$3,$4,$5,$6,$7,$8)")(title, desc, status, pid, tags, creator, time, time);
	const r = db.insertId;
	db.commit();
	return cast(Id)r;
}

/// 判断是否自动接受变更请求
bool canAutoAcceptCR(Id pid, Id creator, string tags, string oldTags) {
	return !pid.isLockedAt(now) &&
		(!oldTags.length || (getUser!"perm"(creator)
				.perm & Perm.editCR))
		&& getPostByTags(tags) == 0;
}

/// 接受变更请求
extern (D) void updateTags(StdTime updatedAt, long pid, Tags tags, Tags oldTags) {
	import jinji.tag.model;

	enum sql = SB.insert!TagCountDelta ~ " ON CONFLICT(name) DO " ~
		SB.update("")
			.set("delta=delta+$2")
			.where("name=$1");
	foreach (tag; Splitter(tags))
		exec!sql(tag, 1);
	foreach (tag; Splitter(oldTags))
		exec!sql(tag, -1);
	tags = applyTagAliases(tags);
	tags = applyImplicitTags(tags);
	exec!(SB.update!Post.set("tags=$1,updated_at=$2").where("id=$3"))(tags, updatedAt, pid);
}
